/*
 * Copyright Konstantin Triger <kostat@gmail.com> 
 * 
 * This file is part of Jaque - JAva QUEry library <http://code.google.com/p/jaque/>.
 * 
 * Jaque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jaque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jaque.expressions;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import jaque.functions.*;
import static jaque.expressions.Functions.*;

final class Interpreter implements
		ExpressionVisitor<Function<?, ? super Object[]>> {

	static final Interpreter Instance = new Interpreter();

	private Interpreter() {
	}

	@SuppressWarnings("unchecked")
	// @Override
	public Function<?, Object[]> visit(BinaryExpression e) {
		final Function<?, Object[]> first = (Function<?, Object[]>) e
				.getFirst().apply(this);
		final Function<?, Object[]> second = (Function<?, Object[]>) e
				.getSecond().apply(this);
		switch (e.getExpressionType()) {
		case ExpressionType.Add:
			return add((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.BitwiseAnd:
			return bitwiseAnd((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.LogicalAnd:
			return and((Function<Boolean, Object[]>) first,
					(Function<Boolean, Object[]>) second);
		case ExpressionType.ArrayIndex:
			return new Function<Object, Object[]>() {
				// // @Override
				public Object invoke(Object[] t) throws Throwable {
					return Array.get(first.invoke(t), (Integer) second
							.invoke(t));
				}
			};
		case ExpressionType.Coalesce:
			return coalesce((Function<?, Object[]>) first,
					(Function<?, Object[]>) second);
		case ExpressionType.Conditional:
			return iif((Function<Boolean, Object[]>) e.getOperator()
					.apply(this), (Function<?, Object[]>) first,
					(Function<?, Object[]>) second);
		case ExpressionType.Divide:
			return divide((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.Equal:
			return equal((Function<? extends Comparable, Object[]>) first,
					(Function<? extends Comparable, Object[]>) second);
		case ExpressionType.ExclusiveOr:
			return xor((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.GreaterThan:
			return greaterThan(
					(Function<? extends Comparable, Object[]>) first,
					(Function<? extends Comparable, Object[]>) second);
		case ExpressionType.GreaterThanOrEqual:
			return greaterThanOrEqual(
					(Function<? extends Comparable, Object[]>) first,
					(Function<? extends Comparable, Object[]>) second);
		case ExpressionType.LeftShift:
			return shiftLeft((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.LessThan:
			return lessThan((Function<? extends Comparable, Object[]>) first,
					(Function<? extends Comparable, Object[]>) second);
		case ExpressionType.LessThanOrEqual:
			// FieldAccess
			// / MethodAccess
			return lessThanOrEqual(
					(Function<? extends Comparable, Object[]>) first,
					(Function<? extends Comparable, Object[]>) second);
		case ExpressionType.Modulo:
			return modulo((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.Multiply:
			// / New
			return multiply((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.NotEqual:
			return not((Function<Boolean, Object[]>) equal(
					(Function<? extends Comparable, Object[]>) first,
					(Function<? extends Comparable, Object[]>) second));
		case ExpressionType.BitwiseOr:
			return bitwiseOr((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.LogicalOr:
			return or((Function<Boolean, Object[]>) first,
					(Function<Boolean, Object[]>) second);
			// case ExpressionType.Power:
			// return power((Function<Number, Object[]>) first,
			// (Function<Number, Object[]>) second);
		case ExpressionType.RightShift:
			return shiftRight((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.Subtract:

			return subtract((Function<Number, Object[]>) first,
					(Function<Number, Object[]>) second);
		case ExpressionType.InstanceOf:
			try {
				return instanceOf(first,
						((Function<Class<?>, Object[]>) second).invoke(null));
			} catch (Throwable t) {
				throw new RuntimeException(t);
			}
		default:
			throw new IllegalArgumentException(ExpressionType.toString(e
					.getExpressionType()));
		}
	}

	// @Override
	public Function<?, Object[]> visit(ConstantExpression e) {
		return constant(e.getValue());
	}

	@SuppressWarnings("unchecked")
	// @Override
	public Function<?, Object[]> visit(InvocationExpression e) {
		InvocableExpression ie = e.getMethod();
		int start = ie instanceof MemberExpression
				&& ((MemberExpression) e.getMethod()).getInstance() != null ? 1
				: 0;
		final Function<?, ? super Object[]> m = e.getMethod().apply(this);

		List<Expression> args = e.getArguments();
		final Function<?, ? super Object[]>[] argsF = new Function[args.size()
				+ start];
		if (start > 0)
			argsF[0] = jaque.expressions.Functions.<Object[]> self();
		for (int i = 0; i < args.size(); i++)
			argsF[i + start] = args.get(i).apply(this);

		return new Function<Object, Object[]>() {
			// // @Override
			public Object invoke(Object[] t) throws Throwable {
				Object[] argValues = new Object[argsF.length];
				for (int i = 0; i < argValues.length; i++)
					argValues[i] = argsF[i].invoke(t);

				return m.invoke(argValues);
			}
		};
	}

	// @Override
	public Function<?, ? super Object[]> visit(LambdaExpression<?> e) {

		final Function<?, ? super Object[]> f = e.getBody().apply(this);

		if (e.getParameters().size() == 1) {
			if (e.getResultType() == Boolean.class)
				return compilePredicate(f, e);

			return compile(f, e);
		}

		return f;
	}

	private static Function<Object, ? super Object[]> compile(
			final Function<?, ? super Object[]> f, final LambdaExpression<?> e) {
		return (Function<Object, ? super Object[]>) new Function<Object, Object>() {
			// @Override
			public Object invoke(Object t) throws Throwable {
				return f
						.invoke(t instanceof Object[]
								&& ((Object[]) t).length == e.getParameters()
										.size() ? (Object[]) t
								: new Object[] { t });
			}

			@Override
			public String toString() {
				return e.toString();
			}
		};
	}

	private Function<?, ? super Object[]> compilePredicate(
			final Function<?, ? super Object[]> f, final LambdaExpression<?> e) {
		return (Function<?, ? super Object[]>) new Predicate<Object>() {
			// @Override
			public Boolean invoke(Object t) throws Throwable {
				return (Boolean) f
						.invoke(t instanceof Object[]
								&& ((Object[]) t).length == e.getParameters()
										.size() ? (Object[]) t
								: new Object[] { t });
			}

			@Override
			public String toString() {
				return e.toString();
			}
		};
	}

	// @Override
	public Function<?, Object[]> visit(MemberExpression e) {
		final Member m = e.getMember();
		Expression ei = e.getInstance();
		final Function<?, ? super Object[]> instance = ei != null ? ei
				.apply(this) : null;
		return m instanceof Field ? new Function<Object, Object[]>() {
			// // @Override
			public Object invoke(Object[] t) throws Throwable {

				return ((Field) m).get(instance == null ? null : instance
						.invoke((Object[]) t[0]));

			}
		} : new Function<Object, Object[]>() {
			// // @Override
			public Object invoke(Object[] t) throws Throwable {
				if (m instanceof Method) {
					Object inst;
					if (instance != null) {
						inst = instance.invoke((Object[]) t[0]);
						Object[] n = new Object[t.length - 1];
						System.arraycopy(t, 1, n, 0, n.length);
						t = n;
					} else
						inst = null;
					return ((Method) m).invoke(inst, t);
				} else
					return ((Constructor<?>) m).newInstance(t);
			}
		};

	}

	// @Override
	public Function<?, Object[]> visit(ParameterExpression e) {
		final int index = e.getIndex();
		return new Function<Object, Object[]>() {
			// // @Override
			public Object invoke(Object[] t) throws Throwable {
				return t[index];
			}
		};
	}

	@SuppressWarnings("unchecked")
	// @Override
	public Function<?, ? super Object[]> visit(UnaryExpression e) {
		final Function<?, ? super Object[]> first = e.getFirst().apply(this);
		switch (e.getExpressionType()) {
		case ExpressionType.ArrayLength:
			return new Function<Object, Object[]>() {
				// // @Override
				public Object invoke(Object[] t) throws Throwable {
					return Array.getLength(first.invoke(t));
				}
			};
		case ExpressionType.BitwiseNot:
			return bitwiseNot((Function<Number, Object[]>) first);
		case ExpressionType.Convert:
			final Class<?> to = e.getResultType();
			if (to.isPrimitive() || Number.class.isAssignableFrom(to))
				return new Function<Object, Object[]>() {
					// @Override
					public Object invoke(Object[] t) throws Throwable {
						Number result = (Number) first.invoke(t);
						if (to.isPrimitive()) {
							if (to == Integer.TYPE)
								return result.intValue();
							if (to == Long.TYPE)
								return result.longValue();
							if (to == Float.TYPE)
								return result.floatValue();
							if (to == Double.TYPE)
								return result.doubleValue();
							if (to == Byte.TYPE)
								return result.byteValue();
							if (to == Character.TYPE)
								return (char) result.intValue();
							if (to == Short.TYPE)
								return result.shortValue();
						} else if (result != null) {
							if (to == BigInteger.class)
								return BigInteger.valueOf(result.longValue());
							if (to == BigDecimal.class)
								return BigDecimal.valueOf(result.doubleValue());
						}
						return to.cast(result);
					}
				};
			return first;
		case ExpressionType.IsNull:
			return new Function<Object, Object[]>() {
				// @Override
				public Object invoke(Object[] t) throws Throwable {
					return first.invoke(t) == null;
				}
			};
		case ExpressionType.LogicalNot:
			return not((Function<Boolean, Object[]>) first);
		case ExpressionType.Negate:
			return negate((Function<? extends Number, Object[]>) first);
		case ExpressionType.Quote:
			return constant(first);
			// case ExpressionType.UnaryPlus:
			// return abs((Function<? extends Number, Object[]>) first);
		default:
			throw new IllegalArgumentException(ExpressionType.toString(e
					.getExpressionType()));
		}
	}
}
